
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>
#include <sys/epoll.h>
#include <netinet/tcp.h>
class Channel;
class Channel
{
public:
    Channel(int fd, bool isListen=false)
        : fd_(fd), islisten_(isListen)
    {
    }

    int getfd()
    {
        return fd_;
    }

    bool islisten()
    {
        return islisten_;
    }

private:
    int fd_;
    bool islisten_ ; // 判断是不是监听端口
};
// void setnonblocking(int fd)
// {
//     int event = fcntl(fd, F_GETFL);
//     if (event == 0)
//     {
//         return;
//     }
//     fcntl(fd, F_SETFL, event | O_NONBLOCK);
// }
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("usage: ./tcpepoll ip port\n");
        printf("example: ./tcpepoll 127.0.0.1 8080\n");
        return -1;
    }
    int listenfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
    if (listenfd < 0)
    {
        // 创建失败
        perror("socket() failed");
        return -1;
    }

    int opt = 1;
    // 前两个是为了ip 和 port可以重复利用
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, static_cast<socklen_t>(sizeof opt)); // 必须的。
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &opt, static_cast<socklen_t>(sizeof opt)); // 有用，但是，在Reactor中意义不大。
    setsockopt(listenfd, SOL_SOCKET, TCP_NODELAY, &opt, static_cast<socklen_t>(sizeof opt));  // 必须的。
    setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, &opt, static_cast<socklen_t>(sizeof opt)); // 可能有用，但是，建议自己做心跳。

    // setnonblocking(listenFd);

    struct sockaddr_in serveraddr;
    // memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]); // 做了两件事 ，第一将字符串变为数值形式 ，另外一件事 大端转发
    serveraddr.sin_port = htons(atoi(argv[2]));

    // int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    if (bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind() failed");
        close(listenfd);
        return -1;
    }

    // 第二个参数是tcp半连接的数量
    if (listen(listenfd, 128) < 0)
    {
        perror("listen() failed");
        close(listenfd);
        return -1;
    }

    // 尽管 size 参数在现代 Linux 内核中已经没有实际作用，但它仍然是一个必需的参数。通常情况下，传递 1 或更大的值是可以接受的。因此，您会看到代码中使用 epoll_create(1)，以符合函数的参数要求。
    int epfd = epoll_create(1);
    Channel *servChannel = new Channel(listenfd, true);
    struct epoll_event ev;
    ev.data.ptr = servChannel;
    // 监听读事件
    ev.events = EPOLLIN;

    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

    struct epoll_event evs[10]; // 这个数据是处理就绪的

    while (true) // 事件循环。
    {
        int infds = epoll_wait(epfd, evs, 10, -1); // 等待监视的fd有事件发生。

        // 返回失败。
        if (infds < 0)
        {
            perror("epoll_wait() failed");
            break;
        }

        // 超时。
        if (infds == 0)
        {
            printf("epoll_wait() timeout.\n");
            continue;
        }

        // 如果infds>0，表示有事件发生的fd的数量。
        for (int ii = 0; ii < infds; ii++) // 遍历epoll返回的数组evs。
        {
            Channel* ch = (Channel*)evs[ii].data.ptr;
            if (evs[ii].events & EPOLLRDHUP) // 对方已关闭，有些系统检测不到，可以使用EPOLLIN，recv()返回0。
            {
                printf("1client(eventfd=%d) disconnected.\n",ch->getfd());
                close(ch->getfd()); // 关闭客户端的fd。
            } //  普通数据  带外数据
            else if (evs[ii].events & (EPOLLIN | EPOLLPRI)) // 接收缓冲区中有数据可以读。
            {
                
                if (ch->islisten()==true) // 如果是listenfd有事件，表示有新的客户端连上来。
                {
                    ////////////////////////////////////////////////////////////////////////
                    struct sockaddr_in clientaddr;
                    socklen_t len = sizeof(clientaddr);
                    int clientfd = accept4(listenfd, (struct sockaddr *)&clientaddr, &len, SOCK_NONBLOCK);

                    printf("accept client(fd=%d,ip=%s,port=%d) ok.\n", clientfd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

                    // 为新客户端连接准备读事件，并添加到epoll中。
                    Channel* clientChannel = new Channel(clientfd);
                    ev.data.ptr=clientChannel;
                    // ev.data.fd = clientfd;
                    ev.events = EPOLLIN | EPOLLET; // 边缘触发。
                    epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
                    ////////////////////////////////////////////////////////////////////////
                }
                else // 如果是客户端连接的fd有事件。
                {
                    ////////////////////////////////////////////////////////////////////////
                    char buffer[1024];
                    while (true) // 由于使用非阻塞IO，一次读取buffer大小数据，直到全部的数据读取完毕。
                    {
                        bzero(&buffer, sizeof(buffer));
                        ssize_t nread = read(ch->getfd(), buffer, sizeof(buffer));
                        if (nread > 0) // 成功的读取到了数据。
                        {
                            // 把接收到的报文内容原封不动的发回去。
                            printf("recv(eventfd=%d):%s\n", ch->getfd(), buffer);
                            send(ch->getfd(), buffer, strlen(buffer), 0);
                        }
                        else if (nread == -1 && errno == EINTR) // 读取数据的时候被信号中断，继续读取。
                        {
                            continue;
                        }
                        else if (nread == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) // 全部的数据已读取完毕。
                        {
                            break;
                        }
                        else if (nread == 0) // 客户端连接已断开。
                        {
                            printf("2client(eventfd=%d) disconnected.\n", ch->getfd());
                            close(ch->getfd()); // 关闭客户端的fd。
                            break;
                        }
                    }
                }
            }
            else if (evs[ii].events & EPOLLOUT) // 有数据需要写，暂时没有代码，以后再说。
            {
            }
            else // 其它事件，都视为错误。
            {
                printf("3client(eventfd=%d) error.\n", ch->getfd());
                close(ch->getfd()); // 关闭客户端的fd。
            }
        }
    }

    return 0;
}